package com.linkgie.galaxyframework.utils.lang;

import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.Callable;

import com.linkgie.galaxyframework.utils.IOStatusException;
import com.linkgie.galaxyframework.utils.annotations.SecuredType;

/**
 * Java 类相关工具类；
 */
@SecuredType
public class JavaClassUtils {

	public static final char CLASSPATH_SEPERATOR_CHAR = '/';

	public static final String CLASSPATH_SEPERATOR = "/";

	/**
	 * 应用的类加载器的名称；
	 */
	@SuppressWarnings("unused")
	private static final String APP_CLASS_LOADER_NAME = "sun.misc.Launcher$AppClassLoader";

	/**
	 * JVM 扩展类加载器的名称；
	 */
	private static final String EXT_CLASS_LOADER_NAME = "sun.misc.Launcher$ExtClassLoader";

	/**
	 * 新的JDK版本下的类加载器的名称；
	 */
	@SuppressWarnings("unused")
	private static final String JDK_APP_CLASS_LOADER_NAME = "jdk.internal.loader.ClassLoaders$AppClassLoader";

	/**
	 * 新的JDK版本下 JVM 扩展类加载器的名称；
	 */
	private static final String JDK_EXT_CLASS_LOADER_NAME = "jdk.internal.loader.ClassLoaders$PlatformClassLoader";

	public static final ClassLoader EXT_CLASS_LOADER;

	static {
		ClassLoader extClassLoader = lookupExtClassLoader();
		if (extClassLoader == null) {
			extClassLoader = lookupJDKExtClassLoader();
		}
		EXT_CLASS_LOADER =  extClassLoader;
	}

	public static ClassLoader getExtClassLoader() {
		return EXT_CLASS_LOADER;
	}

	private static ClassLoader lookupExtClassLoader() {
		ClassLoader classloader = JavaClassUtils.class.getClassLoader();
		while (classloader != null && !EXT_CLASS_LOADER_NAME.equals(classloader.getClass().getName())) {
			classloader = classloader.getParent();
		}
		return classloader;
	}

	private static ClassLoader lookupJDKExtClassLoader() {
		ClassLoader classloader = JavaClassUtils.class.getClassLoader();
		while (classloader != null && !JDK_EXT_CLASS_LOADER_NAME.equals(classloader.getClass().getName())) {
			classloader = classloader.getParent();
		}
		return classloader;
	}

	public static String join(String parent, String... paths) {
		// 注：classpath 不需要截取两头的空格，有些操作系统(比如 MacOS)是允许路径存在空格的，虽然路径存在空格的情形并不常见；
		if (parent == null) {
			parent = "";
		}
		parent = getCanonicalClasspath(parent);

		if (paths == null || paths.length == 0) {
			return parent;
		}
		StringBuilder builder = new StringBuilder(parent);
		for (int i = 0; i < paths.length; i++) {
			String path = paths[i];
			if (path == null) {
				continue;
			}
			path = getCanonicalClasspath(path);
			if (path.length() == 0) {
				continue;
			}

			builder.append(CLASSPATH_SEPERATOR_CHAR);
			builder.append(path);
		}
		return builder.toString();
	}

	/**
	 * 从系统属性 java.class.path 获取当前应用的类路径列表；
	 * 
	 * @return
	 */
	public static String[] getApplicationClasspaths() {
		String classpathProperty = System.getProperty("java.class.path");
		String[] classpaths = classpathProperty.split(":");

		return classpaths;
	}

	/**
	 * 获取类的.class 文件对应的文件系统信息；
	 * 
	 * @return
	 */
	public static ClassPath getClassPath(Class<?> clazz) {
		String classFilePath = clazz.getName().replace('.', '/');
		classFilePath = "/" + classFilePath + ".class";
		return getClassPath(classFilePath, clazz.getClassLoader());
	}

	/**
	 * 获取类路径对应的文件系统信息；
	 * 
	 * @return
	 */
	public static ClassPath getClassPath(String classPath) {
		return getClassPath(classPath, getDefaultClassLoader());
	}

	/**
	 * 获取类路径对应的文件系统信息；
	 * <p>
	 * 如果目标路径不存在， 则返回 null ；
	 * 
	 * 注：如果在类加载器关联的多个 JAR 包中都有指定的类路径(classpath)，最终只会有指向其中一个 JAR 包的 ClassPath 对象返回；
	 * 
	 * @return
	 */
	public static ClassPath getClassPath(String classpath, ClassLoader classloader) {
		if (classloader == null) {
			classloader = getDefaultClassLoader();
		}
		if (classloader == null) {
			throw new IllegalStateException("No classloader!");
		}
		String canonicalClasspath = getCanonicalClasspath(classpath);
		URL classpathUrl = classloader.getResource(canonicalClasspath);
		if (classpathUrl == null) {
			return null;
		}
		return createClasspath(canonicalClasspath, classpathUrl);
	}

	/**
	 * 获取类路径对应的文件系统信息；
	 * <p>
	 * 如果目标路径不存在， 则返回 null ；
	 * 
	 * 注：如果在类加载器关联的多个 JAR 包中都有指定的类路径(classpath)，最终只会有指向其中一个 JAR 包的 ClassPath 对象返回；
	 * 
	 * @return
	 */
	public static ClassPath[] getClassPaths(String classpath, ClassLoader classloader) {
		if (classloader == null) {
			classloader = getDefaultClassLoader();
		}
		if (classloader == null) {
			throw new IllegalStateException("No classloader!");
		}
		try {
			String canonicalClasspath = getCanonicalClasspath(classpath);
			Enumeration<URL> classpathUrls = classloader.getResources(canonicalClasspath);
			List<ClassPath> classpaths = new LinkedList<>();
			while (classpathUrls.hasMoreElements()) {
				URL classpathUrl = classpathUrls.nextElement();
				ClassPath cls = createClasspath(canonicalClasspath, classpathUrl);
				classpaths.add(cls);
			}
			ClassPath[] classpathsArray = new ClassPath[classpaths.size()];
			classpaths.toArray(classpathsArray);
			return classpathsArray;
		} catch (IOException e) {
			throw new IOStatusException(e.getMessage(), e);
		}
	}

	private static ClassPath createClasspath(String canonicalClasspath, URL classpathUrl) {
		String plainUrl = classpathUrl.toExternalForm();
		String proto = classpathUrl.getProtocol();
		boolean inJar = false;
		boolean isFile = false;
		while (proto != null) {
			if ("file".equalsIgnoreCase(proto)) {
				isFile = true;
			} else if ("jar".equalsIgnoreCase(proto)) {
				inJar = true;
			}
			try {
				classpathUrl = new URL(classpathUrl.getFile());
			} catch (MalformedURLException e) {
				break;
			}

			proto = classpathUrl.getProtocol();
		}
		if (!isFile) {
			// 不处理非文件系统协议；
			return null;
		}

		try {
			/**
			 * 截取去掉 jar 包内部的路径，以 !/ 为分隔符；
			 */
			// 文件的完整 URL 路径，包括路径和查询字符；
			String fullPath = classpathUrl.getFile();
			// 文件部分的路径；
			String filePath = fullPath;
			// jar 包内部的路径；
			String entryPath = null;
			int jarPathSeperatorIndex = fullPath.indexOf("!/");
			if (jarPathSeperatorIndex > 0) {
				filePath = fullPath.substring(0, jarPathSeperatorIndex);
				// 内部路径保留了分隔符“!/”中的路径分隔符 / 开头；
				entryPath = fullPath.substring(jarPathSeperatorIndex + 1);
			}

			// 获得文件路径和文件所在目录路径；
			filePath = URLDecoder.decode(filePath, "UTF-8");
			File path = new File(filePath);
			String file = null;
			while (path != null && !path.isDirectory()) {
				if (path.isFile()) {
					file = path.getCanonicalPath();
				}
				path = path.getParentFile();
			}

			String dir = path == null ? null : path.getCanonicalPath();

			ClassPath cpath = new ClassPath();
			// cpath.setBaseClass(clazz);
			cpath.setClassPath(canonicalClasspath);
			cpath.setDir(dir);
			cpath.setFile(file);
			cpath.setInJar(inJar);
			cpath.setUrl(plainUrl);
			cpath.setPath(entryPath);
			return cpath;
		} catch (IOException e) {
			throw new IllegalStateException(e.getMessage(), e);
		}
	}

	/**
	 * 从指定的类路径中检索资源文件的列表；
	 * 
	 * @param path
	 * @return
	 */
	public static List<String> getResourceFiles(String path) {
		List<String> filenames = new ArrayList<>();

		try (InputStream in = getResourceAsStream(path);
				BufferedReader br = new BufferedReader(new InputStreamReader(in))) {
			String resource;

			while ((resource = br.readLine()) != null) {
				filenames.add(resource);
			}
		} catch (IOException e) {
			throw new IllegalStateException(e.getMessage(), e);
		}

		return filenames;
	}

	private static InputStream getResourceAsStream(String resource) {
		final InputStream in = getContextClassLoader().getResourceAsStream(resource);

		return in == null ? ClassPath.class.getResourceAsStream(resource) : in;
	}

	/**
	 * 去掉开头结尾连续的路径分隔符 “/” ；
	 * 
	 * @param classpath
	 * @return
	 */
	public static String getCanonicalClasspath(String classpath) {
		// 注：classpath 不需要截取两头的空格，有些操作系统(比如 MacOS)是允许路径存在空格的，虽然路径存在空格的情形并不常见；
		int length = classpath.length();
		int i = 0;
		int j = length - 1;

		// 去掉开头连续的路径分隔符 / ；
		for (; i <= j; i++) {
			if (classpath.charAt(i) != CLASSPATH_SEPERATOR_CHAR) {
				break;
			}
		}
		// 去掉结尾连续的路径分隔符 / 和空格；
		for (; j > i && j > -1; j--) {
			if (classpath.charAt(j) != CLASSPATH_SEPERATOR_CHAR) {
				break;
			}
		}
		// 截取除去两头路径分隔剩余的部分；
		classpath = classpath.substring(i, j + 1);

		return classpath;
	}

	/**
	 * 以指定的类加载器作为上下文加载执行操作，在操作结束或异常退出后恢复原来的上下文；
	 * 
	 * @param contextLoader
	 * @param runnable
	 */
	public static void switchClassContext(ClassLoader contextLoader, Runnable runnable) {
		// 切换类加载器；
		ClassLoader existingClassLoader = Thread.currentThread().getContextClassLoader();
		try {
			Thread.currentThread().setContextClassLoader(contextLoader);

			runnable.run();
		} finally {
			// 退出恢复上下文；
			Thread.currentThread().setContextClassLoader(existingClassLoader);
		}
	}

	/**
	 * 以指定的类加载器作为上下文加载执行操作，在操作结束或异常退出后恢复原来的上下文；
	 * 
	 * @param contextLoader
	 * @param runnable
	 */
	public static <V> V switchClassContext(ClassLoader contextLoader, Callable<V> runnable) {
		// 切换类加载器；
		ClassLoader existingClassLoader = Thread.currentThread().getContextClassLoader();
		try {
			Thread.currentThread().setContextClassLoader(contextLoader);

			return runnable.call();
		} catch (Exception e) {
			if (e instanceof RuntimeException) {
				throw (RuntimeException) e;
			}
			throw new IllegalStateException(e.getMessage(), e);
		} finally {
			// 退出恢复上下文；
			Thread.currentThread().setContextClassLoader(existingClassLoader);
		}
	}

	private static ClassLoader getContextClassLoader() {
		return Thread.currentThread().getContextClassLoader();
	}

	public static ClassLoader getDefaultClassLoader() {
		ClassLoader cl = null;
		try {
			cl = Thread.currentThread().getContextClassLoader();
		} catch (Throwable ex) {
			// Ignore error that cannot access thread context ClassLoader;
		}
		if (cl == null) {
			// Try to use classloader of this class.
			cl = JavaClassUtils.class.getClassLoader();
			if (cl == null) {
				// Use bootstrap ClassLoader
				try {
					cl = ClassLoader.getSystemClassLoader();
				} catch (Throwable ex) {
					// Ignore error that cannot access system ClassLoader.
				}
			}
		}
		return cl;
	}

}
